package districting;

import java.awt.Color;
import java.io.IOException;
import java.util.*;

import com.vividsolutions.jts.geom.Geometry;

import de.micromata.opengis.kml.v_2_2_0.*;

public abstract class Zone {

   private static final double MAXIMUM_DEVIATION = 0.035;
   
   public static String getHTMLColorString(Color color) {
      String red = Integer.toHexString(color.getRed());
      String green = Integer.toHexString(color.getGreen());
      String blue = Integer.toHexString(color.getBlue());

      return (red.length() == 1 ? "0" + red : red)
            + (green.length() == 1 ? "0" + green : green)
            + (blue.length() == 1 ? "0" + blue : blue);
   }

   // For the bitmaps
   protected static final int DIMENSION = 400;

   // From 2PP data
   protected int votingPopulation;

   protected HashSet<PollingBooth> booths;

   protected HashMap<DivisionMethod, List<Division>> divisions;

   protected int numDivisions;

   protected Geometry pbGeometry;

   // This can be user set (e.g. for Shortest Bisecting Line Method)
   protected Geometry area;

   // This is automatically generated and is just a union of each pollingBooth's
   // polygons
   protected Geometry polygon;

   protected int totalVotes2PP;

   protected int Labor2PPVotes;

   protected int Coalition2PPVotes;

   protected double minLat, maxLat;

   protected double minLong, maxLong;

   protected Color color;

   public void addPB(PollingBooth pb) {
      booths.add(pb);
      votingPopulation += pb.getTotalVotes();
      pbGeometry = pbGeometry.union(pb.getGeometryPoint());

      try {
         minLat = Math.min(minLat, pb.getPoint().getY());
         maxLat = Math.max(maxLat, pb.getPoint().getY());
         minLong = Math.min(minLong, pb.getPoint().getX());
         maxLong = Math.max(maxLong, pb.getPoint().getX());
      } catch (NullPointerException e) {

      }
      try {
         polygon = polygon.union(pb.getPolygon());
      } catch (NullPointerException e) {
         // don't worry. this is only an issue in the first pass through.
      }
   }

   public void removePB(PollingBooth pb) {
      if (booths.contains(pb)) {
         booths.remove(pb);
         votingPopulation -= pb.getTotalVotes();
         pbGeometry = pbGeometry.difference(pb.getGeometryPoint());
         polygon = polygon.difference(pb.getPolygon());
      }
   }

   public PollingBooth getPB(int pbID) {
      for (PollingBooth pb : booths) {
         if (pb.getId() == pbID) {
            return pb;
         }
      }
      throw new NullPointerException();
   }

   /**
    * @return the area
    */
   public Geometry getArea() {
      if (area == null) {
         return polygon;
      } else {
         return area;
      }
   }

   /**
    * @param area
    *           the area to set
    */
   public void setArea(Geometry area) {
      this.area = area;
   }

   /**
    * @return the polygon
    */
   public Geometry getPolygon() {
      return polygon;
   }

   /**
    * @param polygon
    *           the polygon to set
    */
   public void setPolygon(Geometry polygon) {
      this.polygon = polygon;
   }

   /**
    * @return the numDivisions
    */
   public int getNumDivisions() {
      return numDivisions;
   }

   /**
    * @param numDivisions
    *           the numDivisions to set
    */
   public void setNumDivisions(int numDivisions) {
      this.numDivisions = numDivisions;
   }

   /**
    * @return the pbGeometry
    */
   public Geometry getPbGeometry() {
      return pbGeometry;
   }

   /**
    * @return the votingPopulation
    */
   public int getVotingPopulation() {
      return votingPopulation;
   }

   /**
    * @param votingPopulation
    *           the votingPopulation to set
    */
   public void setVotingPopulation(int votingPopulation) {
      this.votingPopulation = votingPopulation;
   }

   /**
    * @return the Coalition2PPVotes
    */
   public int getCoalition2PPVotes() {
      return Coalition2PPVotes;
   }

   /**
    * @return the Labor2PPVotes
    */
   public int getLabor2PPVotes() {
      return Labor2PPVotes;
   }

   /**
    * @return the booths
    */
   public HashSet<PollingBooth> getBooths() {
      return booths;
   }

   /**
    * @return the divisions
    */
   public List<Division> getDivisions(DivisionMethod divMethod) {
      return divisions.get(divMethod);
   }

   public Division getDivision(Integer id, DivisionMethod divMethod) {
      for (Division d : divisions.get(divMethod)) {
         if (d.getId() == id) {
            return d;
         }
      }
      throw new NullPointerException();
   }

   public boolean predictedLabor() {
      return Labor2PPVotes > Coalition2PPVotes;
   }

   public boolean predictedCoalition() {
      return Coalition2PPVotes > Labor2PPVotes;
   }

   public boolean predictedDraw() {
      return Labor2PPVotes == Coalition2PPVotes;
   }

   public void addDivision(Division division, DivisionMethod divMethod) {
      divisions.get(divMethod).add(division);
   }

   public boolean checkNumDivisions(DivisionMethod divMethod) {
      return numDivisions == divisions.get(divMethod).size();
   }

   public void createBitmap(PBProperty pBProperty, String filename)
         throws IOException {
      int resolution = (int) Math.round(Math.max(DIMENSION / (maxLat - minLat),
            DIMENSION / (maxLong - minLong)));
      createBitmap(resolution, pBProperty, filename);
   }

   public void createBitmap(int resolution, PBProperty pBProperty,
         String filename) throws IOException {
      int height = (int) (Math.round(resolution * (maxLat - minLat)) + 1);
      int width = (int) (Math.round(resolution * (maxLong - minLong)) + 1);

      Colorable[][] array = new Colorable[width][height];

      for (int y = 0; y < height; y++) {
         for (int x = 0; x < width; x++) {
            array[x][y] = null;
         }
      }

      for (PollingBooth pb : booths) {
         if (pb.getPoint() != null) {
            int x = (int) Math.round(resolution
                  * (pb.getPoint().getX() - minLong));
            int y = (int) Math.round(resolution
                  * (pb.getPoint().getY() - minLat));
            if (x >= 0 && x < width && y >= 0 && y < height) {
               array[x][y] = pBProperty.getProperty(pb);
            }
         }
      }

      Bitmap.createBitmap(width, height, array, filename);

   }

   public void toKmlBooths(Document document) {
      // document.createAndAddStyle().withId(getHTMLColorString(color)).createAndSetPolyStyle().withColor("B0"+getHTMLColorString(color));
      for (PollingBooth pb : booths) {
         pb.toKml(document, color);
      }
   }

   public void toKmlDivisions(Document document, DivisionMethod divMethod) {
      for (Division division : divisions.get(divMethod)) {
         division.toKml(document);
      }
   }

   public void setAreas(DivisionMethod divMethod) {
      for (Division division : divisions.get(divMethod)) {
         division.setPolygon();
      }
   }
   
   public boolean withinPopulationDeviation(DivisionMethod method) {
      double min = Integer.MAX_VALUE;
      double max = 0;
      double total = 0;
      
      for (Division division : divisions.get(method)) {
         total += division.getVotingPopulation();
         if (division.getVotingPopulation() < min) {
            min = division.getVotingPopulation();
         }
         if (division.getVotingPopulation() > max) {
            max = division.getVotingPopulation();
         }
      }
      double average = total / numDivisions;
      
      if (average * (1 - MAXIMUM_DEVIATION) > min) {
         return false;
      }
      if (average * (1 + MAXIMUM_DEVIATION) < max) {
         return false;
      }
      return true;
   }

}
